﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Runtime.Serialization;

namespace Optimization.Utilities
{
    /// <summary>
    /// Class Vector serves as a storage for math equivalent of vector. It can store list of double items.
    /// It has defined math operators like +, -, *
    /// </summary>
    [Serializable]
    public class Vector : ISerializable
    {
        private double[] value;
        static private System.Random rnd = new Optimization.Utilities.Random();

        /// <summary>
        /// Create random vector.
        /// </summary>
        /// <param name="Dimension">Dimension of vector (length)</param>
        /// <param name="Low">low value of interval from which the random numbers are generated</param>
        /// <param name="High">high value of interval from which the random numbers are generated</param>
        /// <returns></returns>
        public static Vector Random(int Dimension, float Low, float High)
        {
            Vector result = new Vector(Dimension);
            double Delta = High - Low;
            for (int i = 0; i < Dimension; i++)
                result[i] = rnd.NextDouble() * Delta + Low;

            return result;
        }

        /// <summary>
        /// Create random vector.
        /// </summary>
        /// <param name="Low">low value of interval from which the random numbers are generated</param>
        /// <param name="High">high value of interval from which the random numbers are generated</param>
        /// <returns></returns>
        public static Vector Random(Vector Low, Vector High)
        {
            Vector result = new Vector(Low.Length);
            Vector Delta = High - Low;
            for (int i = 0; i < Low.Length; i++)
                result[i] = rnd.NextDouble() * Delta[i];
            result = result + Low;
                 
            return result;
        }

        //http://stackoverflow.com/questions/218060/random-gaussian-variables
        /// <summary>
        /// Create gaussian random vector. 99.6% values are in defined interval. Mean and stdDev are calculated automaticaly.
        /// </summary>
        /// <param name="Low">low value of interval from which the random numbers are generated</param>
        /// <param name="High">high value of interval from which the random numbers are generated</param>
        /// <returns></returns>
        public static Vector RandomNormal996(Vector Low, Vector High)
        {
            Optimization.Utilities.Random rnd = new Optimization.Utilities.Random();
            Vector result = Vector.Zero(Low.Length);

            for (int i = 0; i < result.Length; i++)
            {
                double mean = (Low[i] + High[i]) / 2;
                double stdDev = (High[i] - Low[i]) / 6; // to have 99.6% in bounds

                double u1 = rnd.NextDouble();
                double u2 = rnd.NextDouble();
                double randStdNormal = Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); //random normal(0,1)
                double randNormal = mean + stdDev * randStdNormal; //random normal(mean,stdDev^2)

                result[i] = randNormal;
            }
            return result;
        }

        //http://stackoverflow.com/questions/218060/random-gaussian-variables
        /// <summary>
        /// Create gaussian random vector.
        /// </summary>
        /// <param name="Mean"></param>
        /// <param name="StdDev"></param>
        /// <returns></returns>
        public static Vector RandomNormal(Vector Mean, Vector StdDev)
        {
            Optimization.Utilities.Random rnd = new Optimization.Utilities.Random();
            Vector result = Vector.Zero(Mean.Length);

            for (int i = 0; i < result.Length; i++)
            {
                double mean = Mean[i];
                double stdDev = StdDev[i]; 

                double u1 = rnd.NextDouble();
                double u2 = rnd.NextDouble();
                double randStdNormal = Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); //random normal(0,1)
                double randNormal = mean + stdDev * randStdNormal; //random normal(mean,stdDev^2)

                result[i] = randNormal;
            }
            return result;
        }

        /// <summary>
        /// Create random vector with log distribution.
        /// </summary>
        /// <param name="Low">low value of interval from which the random numbers are generated</param>
        /// <param name="High">high value of interval from which the random numbers are generated</param>
        /// <returns></returns>
        public static Vector RandomLog(Vector Low, Vector High)
        {
            Optimization.Utilities.Random rnd = new Optimization.Utilities.Random();
            Vector result = Vector.Zero(Low.Length);

            Vector logMins = Vector.Zero(Low.Length);
            Vector logMaxs = Vector.Zero(High.Length);

            for (int i = 0; i < result.Length; i++)
            {
                logMaxs[i] = Math.Log(High[i]);
                logMins[i] = Math.Log(Low[i]);
            }

            Vector rd = Vector.Random(result.Length, 0, 1);

            for (int i = 0; i < result.Length; i++)
                result[i] = rd[i] * (logMaxs[i] - logMins[i]) + logMins[i];

            for (int i = 0; i < result.Length; i++)
                result[i] = Math.Exp(result[i]);

            return result;
        }

        /// <summary>
        /// Create Vector with zero values
        /// </summary>
        /// <param name="Dimension"></param>
        /// <returns></returns>
        public static Vector Zero(int Dimension)
        {
            Vector result = new Vector(Dimension);
            for (int i = 0; i < Dimension; i++)
                result[i] = 0;

            return result;
        }

        /// <summary>
        /// Creates Vector with all values set to one (1)
        /// </summary>
        /// <param name="size"></param>
        /// <returns></returns>
        public static Vector One(int size)
        {
            Vector result = new Vector(size);
            for (int i = 0; i < size; i++)
                result[i] = 1;

            return result;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="Dimension">Dimension of created vector</param>
        public Vector(int Dimension)
        {
            value = new double[Dimension];
        }

        /// <summary>
        /// Create vector from double array
        /// </summary>
        /// <param name="values">init values for vector</param>
        public Vector(double[] values)
        {
            int Cnt = values.Length;
            value = new double[Cnt];
            for (int i = 0; i < Cnt; i++)
                value[i] = values[i];
        }

        /// <summary>
        /// Create Vector with dimension 2
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public Vector(double x, double y)
        {
            value = new double[2];
            value[0] = x;
            value[1] = y;
        }


        /// <summary>
        /// Constructor for Vector copying
        /// </summary>
        /// <param name="values">Vector to be copied</param>
        public Vector(Vector values)
        {
            int Cnt = values.value.Length;
            value = new double[Cnt];
            for (int i = 0; i < Cnt; i++)
                value[i] = values.value[i];
        }

        /// <summary>
        /// Direct copy of vector
        /// </summary>
        /// <returns></returns>
        public Vector Copy()
        {
            Vector result = new Vector(value);
            return result;
        }

        /// <summary>
        /// Plus operator with double[] array
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Vector operator +(double[] a, Vector b)
        {
            return new Vector(a) + b;
        }

        /// <summary>
        /// Plus operator for two vectors
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Vector operator +(Vector a, Vector b)
        {
            Vector result = new Vector(a);
            int Cnt = a.value.Length;
            for (int i = 0; i < Cnt; i++)
                result[i] = result[i] + b[i];

            return result;
        }

        /// <summary>
        /// Minus operator for double[] array and Vector
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Vector operator -(double[] a, Vector b)
        {
            return new Vector(a) - b;
        }

        /// <summary>
        /// Minus operator for two Vectors
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Vector operator -(Vector a, Vector b)
        {
            Vector result = new Vector(a);
            int Cnt = a.value.Length;
            for (int i = 0; i < Cnt; i++)
                result[i] = result[i] - b[i];

            return result;
        }

        /// <summary>
        /// Multiplying of Vector with double value
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Vector operator *(double a, Vector b)
        {
            Vector result = new Vector(b);
            int Cnt = b.value.Length;
            for (int i = 0; i < Cnt; i++)
                result[i] = result[i] * a;

            return result;
        }

        /// <summary>
        /// Multiplying of Vector with double value
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static Vector operator *(Vector a, float b)
        {
            return b * a;
        }

        /// <summary>
        /// Absolut value of Vector (size of Vector), SQRT from Sum of squares
        /// </summary>
        /// <returns></returns>
        public double Abs()
        {
            double result = 0;
            int Cnt = value.Length;
            for (int i = 0; i < Cnt; i++)
                result = result + this[i] * this[i];

            return Math.Sqrt(result);
        }

        /// <summary>
        /// Indexer, allows to access to values inside of Vector
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public double this[int index]
        {
            get
            {
                return value[index];
            }
            set
            {
                this.value[index] = value;
            }
        }

        /// <summary>
        /// String representation of Vector in form [value0 value1 value2 ... ].
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder result = new StringBuilder();
            result.Append('[');

            int Cnt = value.Length;
            for (int i = 0; i < Cnt; i++)
            {
                if (i > 0)
                    result.Append(' ');
                result.Append(this[i]);
            }

            result.Append(']');

            return result.ToString();
        }

        /// <summary>
        /// Converting Vector into double[] array. Allows to use wide set of functions, build T:IEnumerator{double}, convert into T:List{double}, etc.
        /// </summary>
        /// <returns></returns>
        public double[] ToArray()
        {
            return value.Select((d) => (d)).ToArray();
        }

        /// <summary>
        /// Returns dimension of Vector.
        /// </summary>
        public int Length { get { return value.Length; } }

        /// <summary>
        /// Allow to use strings for set and get Vector value
        /// </summary>
        public string Value { get { return ToString(); } set { setValue(value); } }

        private void setValue(string str)
        {
            string prepared = str.Replace("[", "").Replace("]", "");
            string[] items = prepared.Split(' ');
            value = items.Select(cItem => double.Parse(cItem)).ToArray();
        }

        /// <summary>
        /// Method for parsing string value into Vector. Like double.Parse() method.
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static Vector Parse(string s)
        {
            string prepared = s.Replace("[", "").Replace("]", "");
            string[] items = prepared.Split(' ');
            double[] values = items.Select(cItem => double.Parse(cItem)).ToArray();

            return new Vector(values);
        }

        // Implement this method to serialize data. The method is called  
        // on serialization.
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            string data = value.Select((v) => v.ToString(System.Globalization.CultureInfo.InvariantCulture)).Aggregate((s1, s2) => s1 + ";" + s2);
            info.AddValue("value", "[" + data + "]", typeof(string));
            //throw new NotImplementedException();
        }

        /// <summary>
        /// The special constructor is used to deserialize values.  
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        public Vector(SerializationInfo info, StreamingContext context)
        {
            // Reset the property value using the GetValue method.
            string data = (string)info.GetValue("value", typeof(string));
            data = data.Replace("[", "").Replace("]", "");
            string[] items = data.Split(';');
            double[] values = items.Select((s) => double.Parse(s, System.Globalization.CultureInfo.InvariantCulture)).ToArray();
            value = values;
        }

        /// <summary>
        /// Constructor for serialization allowance
        /// </summary>
        private Vector()
        {
        }
    }

    /// <summary>
    /// Extension class. Support converting from double[] array into Vector
    /// </summary>
    public static class Extensions
    {
        /// <summary>
        /// Extension method for double[] array. Converts array into Vector.
        /// </summary>
        /// <param name="values"></param>
        /// <returns></returns>
        static public Vector ToVector(this double[] values)
        {
            Vector result = new Vector(values);

            return result;
        }
    }

}
